/*!
   \file

   \brief
      A 2D parallelogram.

   © 2011 Mark W. Gabby II

   This file is part of the Avion engine.

   The Avion engine is free software: you can redistribute it and/or modify it under the terms of
   the GNU General Public License as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.

   The Avion engine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
   the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along with the Avion Engine.
   If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <assert.h>

#include "Math/Math.hpp"
#include "Math/PointN.hpp"
#include "Math/VectorN.hpp"
#include "Math/AABB.hpp"

/*!
    \brief
    Represents a parallelogram in 2D space. Note that squares and rectangles are special cases
    of parallelograms.

*/
template< typename type = float >
class Parallelogram
{
public:
   Parallelogram();

   // Creates a parallelogram with a area of zero
   Parallelogram( const PointN<2, type> & Origin );

   // Creates a square
   Parallelogram( const PointN<2, type> & Origin, const type SideLength );
   // Creates a rectangle
   Parallelogram( const PointN<2, type> & Origin, const type WidthX, const type HeightY );

   // These three construct aligned parallelograms that have one side parallel to the X axis.
   Parallelogram( const PointN<2, type> & CreationOrigin, const type WidthX, const VectorN<2, type> & Vector2 );
   Parallelogram( const PointN<2, type> & CreationOrigin, const type WidthX, const CoordPair<type> & PolarCoordinate );
   // This last one of the three doesn't use a second vector - instead it has a lean angle.
   Parallelogram( const PointN<2, type> & Origin, const type SideWidthX, const type SideSeparationDistY, const type LeanAngleRadsCCW );

   // This function constructs an arbitrary parallelogram.
   Parallelogram( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 );

   // Creates a parallelogram from a 2D axis-aligned bounding box.
   Parallelogram( const AABB<2, type> & SourceAABB );

   bool IsAlignedToX() const;

   bool PointContained( const PointN<2, type> & Point );

   type GetArea() const;
   type GetHeight() const;
   type GetDistanceAcross( const type DistanceDown = 0 ) const;
   type GetOffsetX( const type DistanceDown ) const;
   // Setting the origin is safe, because it doesn't affect the side ordering.
   void SetOrigin( const PointN<2, type> & NewOrigin )
   {
      origin = NewOrigin;
   }
   void Translate( const VectorN<2, type> & Translation )
   {
      origin += Translation;
   }

   const PointN<2, type> & GetOrigin() const
   {
      return origin;
   }
   const VectorN<2, type> & GetSideA() const
   {
      return sideA;
   }
   const VectorN<2, type> & GetSideB() const
   {
      return sideB;
   }
private:

   void PosXPosX( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Degenerate parallelogram.
      Warning( "Degenerated parallelogram created." );
   }
   void PosXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // We create a rectangle with the creation origin shifted up by Vector2, which is positive along the Y axis.
      const type & WidthX = Vector1.x();
      const type & HeightY = Vector2.y();

      *this = Parallelogram( PointN<2, type>( CreationOrigin.x(), CreationOrigin.y() + Vector2.y() ), WidthX, HeightY );
   }
   void PosXNegX( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Degenerate parallelogram.
      Warning( "Degenerated parallelogram created." );
   }
   void PosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Note that Vector2's Y is used for the height so we must negative it for it to be positive.
      const type & WidthX = Vector1.x();
      const type & HeightY = -Vector2.y();

      *this = Parallelogram( CreationOrigin, WidthX, HeightY );
   }
   void PosXandPosXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      *this = Parallelogram( CreationOrigin, Vector1.x(), Vector2 );
   }
   void PosXandNegXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      *this = Parallelogram( CreationOrigin, Vector1.x(), Vector2 );
   }
   void PosXandNegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      *this = Parallelogram( CreationOrigin, Vector1.x(), Vector2 );
   }
   void PosXandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      *this = Parallelogram( CreationOrigin, Vector1.x(), Vector2 );
   }

   void PosYPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Degenerate parallelogram.
      Warning( "Degenerated parallelogram created." );
   }
   void PosYNegX( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Rectangle with the wrong corner.
      const type & WidthX = -Vector2.x();
      const type & HeightY = Vector1.y();

      PointN<2, type> NewOrigin( CreationOrigin.x() + Vector2.x(), CreationOrigin.y() + Vector1.y() );

      *this = Parallelogram( NewOrigin, WidthX, HeightY );
   }
   void PosYNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Degenerate parallelogram.
      Warning( "Degenerated parallelogram created." );
   }
   void PosYandPosXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // This is the bottom half of the parallelogram:
      // ^  ^
      // | /
      // |/
      // O
      // We need to make it the top half
      // Store the reverse of the vectors
      sideA = -Vector2;
      sideB = -Vector1;

      // Move the origin to the top half
      origin = ( CreationOrigin + Vector1 ) + Vector2;
   }
   void PosYandNegXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      sideA = -Vector1;
      sideB = -Vector2;

      origin = ( CreationOrigin + Vector1 ) + Vector2;
   }
   void PosYandNegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      sideA = Vector2;
      sideB = -Vector1;

      origin = CreationOrigin + Vector1;
   }
   void PosYandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      sideA = -Vector1;
      sideB = Vector2;

      origin = CreationOrigin + Vector1;
   }
   void NegXNegX( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Degenerate parallelogram.
      Warning( "Degenerated parallelogram created." );
   }
   void NegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Rectangle, but the origin is off and the width and height are negative of what they need to be.
      PointN<2, type> NewOrigin( CreationOrigin.x() + Vector1.x(), CreationOrigin.y() );
      const type & WidthX = -Vector1.x();
      const type & HeightY = -Vector2.y();

      *this = Parallelogram( NewOrigin, WidthX, HeightY );
   }
   void NegXandPosXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // One side of the parallelogram is aligned to the X axis, but
      // the origin is incorrect - it needs to be on the left.
      type WidthX = -Vector1.x();

      *this = Parallelogram( PointN<2, type>( CreationOrigin.x() + Vector1.x(), CreationOrigin.y() ), WidthX, Vector2 );
   }
   void NegXandNegXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The vectors we have are the right half of the parallelogram, we need the left half.
      // Also, the origin is in the bottom right instead of the top left.
      sideA = -Vector2;
      sideB = -Vector1;

      origin = ( CreationOrigin + Vector1 ) + Vector2;
   }
   void NegXandNegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The origin is on the right instead of the left. Additionally, Vector1 is in the wrong direction.
      sideA = Vector2;
      sideB = -Vector1;

      origin = CreationOrigin + Vector1;
   }
   void NegXandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The origin is on the right instead of the left. Additionally, Vector1 is in the wrong direction.
      sideA = Vector2;
      sideB = -Vector1;

      origin = CreationOrigin + Vector1;
   }
   void NegYNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Degenerate parallelogram.
      Warning( "Degenerated parallelogram created." );
   }
   void NegYandPosXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // In this case, the second vector is the reverse of what we need and the
      // first vector is correct. However, the origin needs to be at the upmost
      // point.
      sideA = -Vector2;
      sideB = Vector1;

      origin = CreationOrigin + Vector2;
   }
   void NegYandNegXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The origin is between the two vectors instead of at the top. Additionally, Vector2 is in the wrong direction.
      sideA = Vector1;
      sideB = -Vector2;

      origin = CreationOrigin + Vector2;
   }
   void NegYandNegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The vectors and origin are correct - assign the vectors to the side appropriately.
      sideA = Vector2;
      sideB = Vector1;

      origin = CreationOrigin;
   }
   void NegYandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The vectors and origin are correct, and even the order is Vector1 -> sideA and Vector2 -> sideB
      sideA = Vector1;
      sideB = Vector2;

      origin = CreationOrigin;
   }
   void PosXPosYandPosXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Both vectors are in the same quadrant.
      // Find the order by taking the cross product.
      type CP = Cross( Vector1, Vector2 );

      // If the cross product is zero, then the parallelogram is degenerate.
      if ( FloatEpsilonEquals<type>( CP, 0 ) )
      {
         // Degenerate parallelogram!
         Warning( "Degenerated parallelogram created." );
      }
      // If Vector1 x Vector2 is negative, then Vector2 will be used to create sideA.
      else if ( CP < 0 )
      {
         // These vectors currently represent the bottom half of the
         // parallelogram. We need the top half ...
         sideA = -Vector2;
         sideB = -Vector1;
      }
      // If it's positive, then Vector1 will be used to create sideA.
      else // CP > 0
      {
         // These vectors currently represent the bottom half of the
         // parallelogram. We need the top half ...
         sideA = -Vector1;
         sideB = -Vector2;
      }

      // The origin is the topmost point in any case
      origin = ( CreationOrigin + Vector1 ) + Vector2;
   }
   void PosXPosYandNegXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // This case is relatively simple.
      sideA = -Vector2;
      sideB = -Vector1;
      origin = ( CreationOrigin + Vector1 ) + Vector2;
   }
   void PosXPosYandNegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The vectors are in opposite quadrants.
      // Find which side the line from the endpoint of Vector1 to the endpoint of Vector2 lies on.
      // Take the cross product
      type CP = Cross( Vector1, Vector2 );

      // If the cross product is zero, then the parallelogram is degenerate.
      if ( FloatEpsilonEquals<type>( CP, 0 ) )
      {
         // Degenerate parallelogram!
         Warning( "Degenerated parallelogram created." );
      }
      // If Vector1 x Vector2 is negative, then Vector1 will be used to create sideA.
      else if ( CP < 0 )
      {
         // These vectors currently represent the left half of the
         // parallelogram. We need the top half ...
         sideA = -Vector1;
         sideB = Vector2;
      }
      // If it's positive, then Vector2 will be used to create sideA.
      else // CP > 0
      {
         // These vectors currently represent the right half of the
         // parallelogram. We need the top half ...
         sideA = Vector2;
         sideB = -Vector1;
      }

      // The origin needs to be offset to the tip of Vector1, since this is the topmost point
      // in the parallelogram
      origin = CreationOrigin + Vector1;
   }
   void PosXPosYandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // In this case, the vectors represent the left half of the parallelogram. We need them
      // to represent the top half.
      sideA = -Vector1;
      sideB = Vector2;

      origin = CreationOrigin + Vector1;
   }
   void NegXPosYandNegXPosY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Both vectors are in the same quadrant.
      // Find the order by taking the cross product.
      type CP = Cross( Vector1, Vector2 );

      // If the cross product is zero, then the parallelogram is degenerate.
      if ( FloatEpsilonEquals<type>( CP, 0 ) )
      {
         // Degenerate parallelogram!
         Warning( "Degenerated parallelogram created." );
      }
      // If Vector1 x Vector2 is positive, then Vector1 will be used to create sideA.
      else if ( CP > 0 )
      {
         // These vectors currently represent the bottom half of the
         // parallelogram. We need the top half ...
         sideA = -Vector1;
         sideB = -Vector2;
      }
      // If it's negative, then Vector2 will be used to create sideA.
      else // CP < 0
      {
         // These vectors currently represent the bottom half of the
         // parallelogram. We need the top half ...
         sideA = -Vector2;
         sideB = -Vector1;
      }

      // The origin is the topmost point in any case
      origin = ( CreationOrigin + Vector1 ) + Vector2;
   }
   void NegXPosYandNegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // This is the right side of the parallelogram; we need the top.
      sideA = Vector2;
      sideB = -Vector1;

      origin = CreationOrigin + Vector1;
   }
   void NegXPosYandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // The vectors are in opposite quadrants.
      // Find which side the line from the endpoint of Vector1 to the endpoint of Vector2 lies on.
      // Take the cross product
      type CP = Cross( Vector1, Vector2 );

      // If the cross product is zero, then the parallelogram is degenerate.
      if ( FloatEpsilonEquals<type>( CP, 0 ) )
      {
         // Degenerate parallelogram!
         Warning( "Degenerated parallelogram created." );
      }
      // If Vector1 x Vector2 is positive, then Vector2 will be used to create sideA.
      else if ( CP > 0 )
      {
         // These vectors currently represent the right half of the
         // parallelogram. We need the top half ...
         sideA = Vector2;
         sideB = -Vector1;
      }
      // If it's negative, then Vector1 will be used to create sideA.
      else // CP < 0
      {
         // These vectors currently represent the right half of the
         // parallelogram. We need the top half ...
         sideA = -Vector1;
         sideB = Vector2;
      }

      // The origin needs to be offset to the tip of Vector1, since this is the topmost point
      // in the parallelogram
      origin = CreationOrigin + Vector1;
   }
   void NegXNegYandNegXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Both vectors are in the same quadrant.
      // Find the order by taking the cross product.
      type CP = Cross( Vector1, Vector2 );

      // If the cross product is zero, then the parallelogram is degenerate.
      if ( FloatEpsilonEquals<type>( CP, 0 ) )
      {
         // Degenerate parallelogram!
         Warning( "Degenerated parallelogram created." );
      }
      // If Vector1 x Vector2 is positive, then Vector1 will be used to create sideA.
      else if ( CP > 0 )
      {
         sideA = Vector1;
         sideB = Vector2;
      }
      // If it's negative, then Vector2 will be used to create sideA.
      else // CP < 0
      {
         sideA = Vector2;
         sideB = Vector1;
      }

      // The origin is already correct
      origin = CreationOrigin;
   }
   void NegXNegYandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Pretty simple. The vectors are already arranged correctly, as is the origin.
      sideA = Vector1;
      sideB = Vector2;

      origin = CreationOrigin;
   }
   void PosXNegYandPosXNegY( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
   {
      // Both vectors are in the same quadrant.
      // Find the order by taking the cross product.
      type CP = Cross( Vector1, Vector2 );

      // If the cross product is zero, then the parallelogram is degenerate.
      if ( FloatEpsilonEquals<type>( CP, 0 ) )
      {
         // Degenerate parallelogram!
         Warning( "Degenerated parallelogram created." );
      }
      // If Vector1 x Vector2 is positive, then Vector1 will be used to create sideA.
      else if ( CP > 0 )
      {
         sideA = Vector1;
         sideB = Vector2;
      }
      // If it's negative, then Vector2 will be used to create sideA.
      else // CP < 0
      {
         sideA = Vector2;
         sideB = Vector1;
      }

      // The origin is already correct
      origin = CreationOrigin;
   }

   PointN<2, type> origin;
   VectorN<2, type> sideA;
   VectorN<2, type> sideB;
};

template< typename type >
Parallelogram<type>::Parallelogram()
   : origin( 0, 0 ), sideA( 0, 0 ), sideB( 0, 0 )
{}

/*!
    \brief
    Constructs a parallelogram with an area of zero.

    \param Origin
    The origin of the parallelogram.
*/
template< typename type >
Parallelogram<type>::Parallelogram( const PointN<2, type> & Origin )
   : origin( Origin ), sideA( 0, 0 ), sideB( 0, 0 )
{}

/*!
    \brief
    Constructs a square, that is, a parallelogram with four sides of equal length,
    with the two pairs of sides perpendicular to each other.

    \param Origin
    The upper-left side of the parallelogram.

    \param SideLength
    The length of one, and hence all, sides.

    \note
    The height is negated internally.
*/
template< typename type >
Parallelogram<type>::Parallelogram( const PointN<2, type> & Origin, const type SideLength )
   : origin( Origin ), sideA( 0, -SideLength ), sideB( SideLength, 0 )
{
   // SideLength must be positive - it's a length!
   AssertWarning( SideLength > 0, "Can't init a parallelogram with negative side length." );
}

/*!
    \brief
    Constructs a rectangle, that is, a parallelogram in which
    the two pairs of sides are perpendicular to each other.

    \param Origin
    The upper-left side of the parallelogram.

    \param WidthX
    The width of the rectangle. ( X axis )

    \param HeightY
    The height of the rectangle. ( Y axis )

    \note
    The height goes down from the origin, not up.
    It's negated internally: send a positive value.
*/
template< typename type >
Parallelogram<type>::Parallelogram( const PointN<2, type> & Origin, const type WidthX, const type HeightY )
   : origin( Origin ), sideA( 0, -HeightY ), sideB( WidthX, 0 )
{
   // The width and height must be positive.
   AssertWarning( WidthX > 0, "Can't create a parallelogram with negative width." );
   AssertWarning( HeightY > 0, "Can't create a parallelogram with negative height." );
}

/*!
    \brief
    Creates an aligned parallelogram with the passed-in values. This version
    takes a creation origin, a width, and a vector for Side B.

    \param CreationOrigin
    The point to construct the parallelogram from. Side A will be WidthX
    in the positive direction and Side B will go from the creation origin
    to its endpoint.

    \param WidthX
    The width of the parallelogram's X-axis-aligned side.

    \param Vector2
    The vector for the second side. Must not be parallel to the X axis
    ( within float epsilon. )

    \note
    Note that once a parallelogram is created, its origin will
    be in its upper-left corner: not necessarily at the point you specify
    in CreationOrigin.
*/
template< typename type >
Parallelogram<type>::Parallelogram( const PointN<2, type> & CreationOrigin, const type WidthX, const VectorN<2, type> & Vector2 )
   : sideB( WidthX, 0 )
{
   // It's a width - it must be positive!
   AssertWarning( WidthX > 0 );
   // If this is true, we're creating a degenerate parallelogram.
   AssertWarning( FloatEpsilonEquals<type>( Vector2.y(), 0 ) == false );

   if ( Vector2.y() < 0 )
   {
      sideA = Vector2;
      origin = CreationOrigin;
   }
   // If the Y value of Vector2 is positive, then the origin will be CreationOrigin + Vector2 rather
   // than just CreationOrigin.
   else if ( Vector2.y() > 0 )
   {
      sideA = -Vector2;
      origin = CreationOrigin + Vector2;
   }
}

/*!
    \brief
    Creates an aligned parallelogram with the passed-in values. This version
    takes a creation origin, a width, and, for Side B, a vector in the form
    of an angle from X+ and a magnitude.

    \param CreationOrigin
    The origin from which to create the parallelogram.

    \param WidthX
    The width of the parallelogram's top and bottom sides, which will be
    parallel to the X axis.

    \param MagnitudeSideB
    The length of the second side.

    \param AngleRadsCCW
    An angle, in radians, which represents the direction of the second side
    of the parallelogram. ( Side B )

    \note
    Note that once a parallelogram is created, its origin will
    be in its upper-left corner: not necessarily at the point you specify
    in CreationOrigin.
*/
template< typename type >
Parallelogram<type>::Parallelogram( const PointN<2, type> & CreationOrigin, const type WidthX, const CoordPair<type> & PolarCoordinate )
{
   // Create the vector for the other side and then pass this result to the other constructor
   VectorN<2, type> Vector2 = PolarToCartesianVec( PolarCoordinate[kRho], PolarCoordinate[kTheta] );

   *this = Parallelogram( CreationOrigin, WidthX, Vector2 );
}

/*!
    \brief
    Creates an aligned parallelogram with the passed-in values. This version
    takes the parallelogram's origin, a width, and a lean angle.
    Note that the length of Side B will NOT be equal to the Y axis height
    unless the angle leads completely downward - in which case you should
    be using the rectangle constructor instead, if possible.

    \param Origin
    The upper-left corner of the parallelogram.

    \param SideWidthX
    The width is the length of the top and bottom sides of the parallelogram.
    These sides are parallel to the X axis.

    \param SideSeparationDistY
    This is the distance between the top and bottom sides along the Y axis.

    \param LeanAngleRadsCCW
    An angle, in radians, which represents the lean angle of the parallelogram.
    This angle is counterclockwise from the X- axis.

    \note
    For LeanAngleRadsCCW:
    Angles greater than Pi will "wrap around."
    Zero and any multiple of Pi will result in a degenerate parallelogram.
*/
template< typename type >
Parallelogram<type>::Parallelogram( const PointN<2, type> & Origin,
                                    const type SideWidthX, const type SideSeparationDistY, const type LeanAngleRadsCCW )
{
   // If the sine is within our margin of error of zero, these values are going to give a degenerate
   // parallelogram. This is because the tangent is the cosine over the sine.
   type SineAngle = sin( LeanAngleRadsCCW );
   type CosAngle = cos( LeanAngleRadsCCW );
   AssertWarning( FloatEpsilonEquals<type>( SineAngle, 0 ) == false );
   // Negative distances are nonsensical in this case.
   AssertWarning( SideSeparationDistY > 0 );

   // Get the other side - ( Note that on creation, it's not necessarily correct. )
   VectorN<2, type> Otherside( 0, SideSeparationDistY );

   if ( FloatEpsilonEquals( fabs( SineAngle ), 1.0 ) == false )
   {
      Otherside.x() = ( CosAngle / SineAngle ) * SideSeparationDistY;
   }

   // If the other side is pointing up ( positive ) then negate it, so that the passed-in origin is indeed the origin of the
   // parallelogram.
   if ( Otherside.y() > 0 )
   {
      Otherside = -Otherside;
   }

   *this = Parallelogram( Origin, SideWidthX, Otherside );
}

template< typename type >
Parallelogram<type>::Parallelogram( const PointN<2, type> & CreationOrigin, const VectorN<2, type> & Vector1, const VectorN<2, type> & Vector2 )
{
   // Find the quadrant the two vectors are in.
   const uint8_t QuadVec1 = GetQuadrant( Vector1 );
   const uint8_t QuadVec2 = GetQuadrant( Vector2 );

   // Do a search based on the quadrants of each, finding the combinations
   // and special-casing for each
   /*
    Y+
    |
    |
    O----- X+ -> V1
   */
   if ( QuadVec1 == kPositiveX )
   {
      switch ( QuadVec1 )
      {
      case kPositiveX:
      {
         PosXPosX( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegativeX:
      {
         PosXNegX( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPositiveY:
      {
         PosXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegativeY:
      {
         PosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXPosY:
      {
         PosXandPosXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXPosY:
      {
         PosXandNegXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXNegY:
      {
         PosXandNegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXNegY:
      {
         PosXandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
   /*
    Y+  ^ V1
    |  /
    | /
    O----- X+
   */
   else if ( QuadVec1 == kPosXPosY )
   {
      switch ( QuadVec2 )
      {
      case kPositiveX:
      {
         PosXandPosXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeX:
      {
         NegXandPosXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPositiveY:
      {
         PosYandPosXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeY:
      {
         NegYandPosXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPosXPosY:
      {
         PosXPosYandPosXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXPosY:
      {
         PosXPosYandNegXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXNegY:
      {
         PosXPosYandNegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXNegY:
      {
         PosXPosYandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
   /*
    ^ V1
    |
    Y+
    |
    |
    O----- X+
   */
   else if ( QuadVec1 == kPositiveY )
   {
      switch ( QuadVec2 )
      {
      case kPositiveX:
      {
         PosXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeX:
      {
         PosYNegX( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPositiveY:
      {
         PosYPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegativeY:
      {
         PosYNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXPosY:
      {
         PosYandPosXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXPosY:
      {
         PosYandNegXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXNegY:
      {
         PosYandNegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXNegY:
      {
         PosYandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
   /*
       V1^   Y+
          \  |
           \ |
    X- ------O----- X+
   */
   else if ( QuadVec1 == kNegXPosY )
   {
      switch ( QuadVec2 )
      {
      case kPositiveX:
      {
         PosXandNegXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeX:
      {
         NegXandNegXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPositiveY:
      {
         PosYandNegXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeY:
      {
         NegYandNegXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;

      case kPosXPosY:
      {
         PosXPosYandNegXPosY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegXPosY:
      {
         NegXPosYandNegXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXNegY:
      {
         NegXPosYandNegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXNegY:
      {
         NegXPosYandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
   /*
                  Y+
                  |
                  |
    V1 <- X- -----O-----X+
   */
   else if ( QuadVec1 == kNegativeX )
   {
      switch ( QuadVec2 )
      {
      case kPositiveX:
      {
         PosXNegX( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeX:
      {
         NegXNegX( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPositiveY:
      {
         PosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeY:
      {
         NegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXPosY:
      {
         NegXandPosXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXPosY:
      {
         NegXandNegXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXNegY:
      {
         NegXandNegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXNegY:
      {
         NegXandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
   /*
            Y+
            |
            |
    X- -----O-----X+
          / |
         /  |
     V1 v   Y-
   */
   else if ( QuadVec1 == kNegXNegY )
   {
      switch ( QuadVec2 )
      {
      case kPositiveX:
      {
         PosXandNegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeX:
      {
         NegXandNegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPositiveY:
      {
         PosYandNegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeY:
      {
         NegYandNegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPosXPosY:
      {
         PosXPosYandNegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegXPosY:
      {
         NegXPosYandNegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegXNegY:
      {
         NegXNegYandNegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXNegY:
      {
         NegXNegYandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
   /*
            Y+
            |
            |
    X- -----O-----X+
            |
            |
            Y-
            |
         V1 v
   */
   else if ( QuadVec1 == kNegativeY )
   {
      switch ( QuadVec2 )
      {
      case kPositiveX:
      {
         PosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeX:
      {
         NegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPositiveY:
      {
         PosYNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeY:
      {
         NegYNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXPosY:
      {
         NegYandPosXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXPosY:
      {
         NegYandNegXPosY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kNegXNegY:
      {
         NegYandNegXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      case kPosXNegY:
      {
         NegYandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
   /*
            Y+
            |
            |
    X- -----O-----X+
            | \
            |  \
            Y-  v V1
   */
   else // if ( QuadVec1 == kPosXNegY )
   {
      switch ( QuadVec2 )
      {
      case kPositiveX:
      {
         PosXandPosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeX:
      {
         NegXandPosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPositiveY:
      {
         PosYandPosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegativeY:
      {
         NegYandPosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;

      case kPosXPosY:
      {
         PosXPosYandPosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegXPosY:
      {
         NegXPosYandPosXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kNegXNegY:
      {
         NegXPosYandNegXNegY( CreationOrigin, Vector2, Vector1 );
      }
      break;
      case kPosXNegY:
      {
         PosXNegYandPosXNegY( CreationOrigin, Vector1, Vector2 );
      }
      break;
      default:
      {
         // Undefined case!
         Warning( "Unhandled case in parallelogram creation!" );
      }
      break;
      }
   }
}

/*!
    \brief
    Creates a parallelogram from a 2D axis-aligned bounding box.

    \param SourceAABB
    The bounding box to use to create this parallelogram.
*/
template< typename type >
Parallelogram<type>::Parallelogram( const AABB<2, type> & SourceAABB )
{

   // 2D AABB:
   //        Y+
   // <-a____|____ Max
   //   |    |    |
   // --|----|----|-- X+
   //   |    |    |
   //   |____|____|
   // Min    |    v b
   //
   // The origin is Max,
   // Side A is the X component of Min and the Y component of Max, and
   // Side B is the X component of Max and the Y component of Min.
   PointN<2, type> Min = SourceAABB.GetMin();
   PointN<2, type> Max = SourceAABB.GetMax();

   PointN<2, type> PointA( Min.x(), Max.y() );
   PointN<2, type> PointB( Max.x(), Min.y() );

   origin = PointA;

   sideA = Min - PointA;
   sideB = Max - PointA;
}

template< typename type >
bool Parallelogram<type>::PointContained( const PointN<2, type> & Point )
{
   // Get a vector to the point from the origin
   VectorN<2, type> ToPoint = Point - origin;

   // Now, find out if the vector is inside the parallelogram by finding the coefficients of the
   // linear combination of sideA and sideB that results in the vector and checking to see if they're
   // both between 0 and 1.
   type Divisor = ( sideA.x() * sideB.y() ) - ( sideA.y() * sideB.x() );

   type Alpha = ( ( ToPoint.x() * sideB.y() ) - ( ToPoint.y() * sideB.x() ) ) / Divisor;

   // If one is outside, no need to calculate the other
   if ( Alpha <= 1.0 && Alpha >= 0 )
   {
      type Beta = ( ( ToPoint.y() * sideA.x() ) - ( ToPoint.x() * sideA.y() ) ) / Divisor;

      // If both are inside, then the point's contained.
      if ( Beta <= 1.0 && Beta >= 0 )
      {
         return true;
      }

   }

   return false;
}

template< typename type >
bool Parallelogram<type>::IsAlignedToX() const
{
   // If the Y component of sideB is zero, that means it's parallel
   // to the X axis and therefore this parallelogram is aligned to X.
   return FloatEpsilonEquals<type>( sideB.y(), 0 );
}

/*!
    \brief
    Returns the height of the parallelogram in units. This
    number is a height so it's positive.
*/
template< typename type >
type Parallelogram<type>::GetHeight() const
{
   // The Y component of the vector sum of sideA and sideB is the distance
   // down the parallelogram, however it will be negative.
   // Since this is the height, return the negative of this sum to assure a
   // positive number.
   return -( sideA.y() + sideB.y() );
}

/*!
    \brief
    Returns the area of the parallelogram, in units squared.
*/
template< typename type >
type Parallelogram<type>::GetArea() const
{
   // The the magnitude of the cross product of the two
   // vectors making up a parallelogram is the area.
   // Luckily, in the 2D case, the magnitude _is_ the one
   // non-zero value of the cross product result- so we
   // don't have to use Sqrt() to get the length.
   return Abs<type>( Cross( sideA, sideB ) );
}
/*!
    \brief
    Given a distance down on the parallelogram, this returns
    the distance across the parallelogram at that point.

    \param DistanceDown
    Distance down the parallelogram. Must be positive, because
    it's a distance.
*/
template< typename type >
type Parallelogram<type>::GetDistanceAcross( const type DistanceDown ) const
{
   AssertWarning( DistanceDown >= 0, "Getting distance across is meaningless with a negative distance down." );

   // Check to see if we're an aligned parallelogram
   if ( this->IsAlignedToX() )
   {
      return sideB.x();
   }
   else
   {
      // Not supported! Distance across only works for aligned parallelograms
      // right now.
      Warning( "Distance across for non-aligned parallelograms isn't supported."
               "Add it if you want it." );
   }

   return 0;
}

/*!
    \brief
    Given a distance down on the parallelogram, this returns
    the distance at that point that you'd have to offset in the
    X to be at the edge.

    \param DistanceDown
    Distance down the parallelogram. Must be positive, because
    it's a distance.
*/
template< typename type >
type Parallelogram<type>::GetOffsetX( const type DistanceDown ) const
{
   AssertWarning( DistanceDown >= 0, "Getting X offset is meaningless with a negative distance down." );

   // Check to see if we're an aligned parallelogram
   if ( this->IsAlignedToX() )
   {
      // Divide-by-zero error... the parallelogram is degenerate
      // in this case
      AssertWarning( FloatEpsilonEquals<type>( sideA.y(), 0 ) == false, "Degenerate parallelogram, can't get offset." );

      return DistanceDown * ( sideA.x() / sideA.y() );
   }
   else
   {
      // Not supported! Distance offset only works for aligned parallelograms
      // right now.
      Warning( "Offset X for non-aligned parallelograms isn't supported."
               "Add it if you want it." );
   }

   return 0;
}
